package com.key.puzzlemodel.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;

import com.key.puzzlemodel.R;

/**
 * 基于ImageView实现的图片裁剪View
 */
public class CropImageView extends AppCompatImageView {

    private RectF mCropRect;// 裁剪区域
    private RectF mCropRectOrigin;// 裁剪区域
    private PointF[] mCropPoints;// 裁剪区域四个顶点
    private PointF mCurPoint;// 当前手指触摸点
    private PointF mLastPoint;// 上一次手指触摸点

    private Path mPath;
    private Paint mPaint;
    private float[] mValues;

    /**
     * 拖拽类型
     * -1, 拖拽裁剪区域
     * 0, 拖拽裁剪区域left,bottom顶点
     * 1, 拖拽裁剪区域left中点
     * 2, 拖拽裁剪区域left,top顶点
     * 3, 拖拽裁剪区域top中点
     * 4, 拖拽裁剪区域right,top顶点
     * 5, 拖拽裁剪区域right中点
     * 6, 拖拽裁剪区域right, bottom中点
     * 7, 拖拽裁剪区域bottom中点
     * 999, 未设定值
     */
    private int mDragType = DRAG_TYPE_NONE;
    private boolean isCropRectFill;

    private final boolean showHotCircle;// 是否显示裁剪框顶点和中点热区（圆形）的半径
    private final int lineColor;// 裁剪框相关线条颜色
    private final int borderWidth;// 裁剪边框线条宽度
    private final int dividerWidth;// 裁剪框内分割线宽度

    private final static int DRAG_TYPE_NONE = 999;
    private final int hotRadius;// 关键点热区圆半径
    private final int markSpace;// 关键点标记与裁剪区域的间隔
    private final int markLengthVertex;// 顶点2个方向的标记线长度
    private final int markLengthMiddle;// 中点2个方向的标记线长度

    private final int minCropWidth;// 裁剪框的最小宽度
    private final int minCropHeight;// 裁剪框的最小高度

    public CropImageView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CropImageView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CropImageView);
        showHotCircle = a.getBoolean(R.styleable.CropImageView_showHotCircle, false);
        lineColor = a.getColor(R.styleable.CropImageView_lineColor, Color.BLACK);
        borderWidth = a.getInteger(R.styleable.CropImageView_borderStrokeWidth, 5);
        dividerWidth = a.getInteger(R.styleable.CropImageView_dividerStrokeWidth, 2);
        hotRadius = a.getInteger(R.styleable.CropImageView_hotRadius, 50);
        markSpace = a.getInteger(R.styleable.CropImageView_markSpace, 8);
        markLengthVertex = a.getInteger(R.styleable.CropImageView_markLengthVertex, 20);
        markLengthMiddle = a.getInteger(R.styleable.CropImageView_markLengthMiddle, 20);
        minCropWidth = a.getInteger(R.styleable.CropImageView_minCropWidth, 150);
        minCropHeight = a.getInteger(R.styleable.CropImageView_minCropHeight, 150);
        a.recycle();

        init();
    }

    @Override
    public void setScaleType(ScaleType scaleType) {
        super.setScaleType(ScaleType.CENTER_INSIDE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (null != getDrawable()) {

            // 记录图片的显示区域
            if (!isCropRectFill) {
                Rect rect = getDrawable().getBounds();
                getImageMatrix().getValues(mValues);

                float scaleX = mValues[Matrix.MSCALE_X];
                float scaleY = mValues[Matrix.MSCALE_Y];

                float startX = mValues[Matrix.MTRANS_X];
                float startY = mValues[Matrix.MTRANS_Y];

                mCropRect.set(startX, startY, startX + scaleX * rect.width(), startY + scaleY * rect.height());

                updateCropRectPoints();

                isCropRectFill = true;

                mCropRectOrigin.set(mCropRect.left, mCropRect.top, mCropRect.right, mCropRect.bottom);
            }

            // 绘制分割线
            mPaint.setStrokeWidth(dividerWidth);
            for (int i = 1; i < 3; ++i) {
                // 横向的分割竖线
                float startXHorizontal = mCropRect.left + i * mCropRect.width() / 3;
                canvas.drawLine(startXHorizontal, mCropRect.top, startXHorizontal, mCropRect.bottom, mPaint);

                // 竖向的分割横线
                float startYVertical = mCropRect.top + i * mCropRect.height() / 3;
                canvas.drawLine(mCropRect.left, startYVertical, mCropRect.right, startYVertical, mPaint);
            }

            // 绘制裁剪区域
            mPaint.setStrokeWidth(borderWidth);
            canvas.drawRect(mCropRect, mPaint);

            // 绘制Resize热点标记
            mPath.reset();

            // left,bottom顶点
            mPath.moveTo(mCropRect.left + markSpace + markLengthVertex, mCropRect.bottom - markSpace);
            mPath.lineTo(mCropRect.left + markSpace, mCropRect.bottom - markSpace);
            mPath.lineTo(mCropRect.left + markSpace, mCropRect.bottom - markSpace - markLengthVertex);

            // left中点
            mPath.moveTo(mCropRect.left + markSpace, mCropRect.top + mCropRect.height() / 2 - markLengthMiddle);
            mPath.lineTo(mCropRect.left + markSpace, mCropRect.top + mCropRect.height() / 2 + markLengthMiddle);

            // left,top顶点
            mPath.moveTo(mCropRect.left + markSpace, mCropRect.top + markSpace + markLengthVertex);
            mPath.lineTo(mCropRect.left + markSpace, mCropRect.top + markSpace);
            mPath.lineTo(mCropRect.left + markSpace + markLengthVertex, mCropRect.top + markSpace);

            // top中点
            mPath.moveTo(mCropRect.left + mCropRect.width() / 2 - markLengthMiddle, mCropRect.top + markSpace);
            mPath.lineTo(mCropRect.left + mCropRect.width() / 2 + markLengthMiddle, mCropRect.top + markSpace);

            // right,top顶点
            mPath.moveTo(mCropRect.right - markSpace - markLengthVertex, mCropRect.top + markSpace);
            mPath.lineTo(mCropRect.right - markSpace, mCropRect.top + markSpace);
            mPath.lineTo(mCropRect.right - markSpace, mCropRect.top + markSpace + markLengthVertex);

            // right中点
            mPath.moveTo(mCropRect.right - markSpace, mCropRect.top + mCropRect.height() / 2 - markLengthMiddle);
            mPath.lineTo(mCropRect.right - markSpace, mCropRect.top + mCropRect.height() / 2 + markLengthMiddle);

            // right,bottom顶点
            mPath.moveTo(mCropRect.right - markSpace, mCropRect.bottom - markSpace - markLengthVertex);
            mPath.lineTo(mCropRect.right - markSpace, mCropRect.bottom - markSpace);
            mPath.lineTo(mCropRect.right - markSpace - markLengthVertex, mCropRect.bottom - markSpace);

            // bottom中点
            mPath.moveTo(mCropRect.left + mCropRect.width() / 2 - markLengthMiddle, mCropRect.bottom - markSpace);
            mPath.lineTo(mCropRect.left + mCropRect.width() / 2 + markLengthMiddle, mCropRect.bottom - markSpace);
            canvas.drawPath(mPath, mPaint);

            // for test only
            if (showHotCircle) {
                mPaint.setColor(Color.BLUE);
                for (PointF p : mCropPoints) {
                    canvas.drawCircle(p.x, p.y, hotRadius, mPaint);
                }
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int action = event.getActionMasked();
        final int pointerIndex = event.getPointerId(0);
        try {
            mCurPoint.set(event.getX(pointerIndex), event.getY(pointerIndex));
            switch (action) {
                case MotionEvent.ACTION_DOWN: {
                    // 判断是否为裁剪区的四个顶点
                    mDragType = DRAG_TYPE_NONE;
                    for (int i = 0; i < mCropPoints.length; ++i) {
                        if (hotRadius >= distance(mCropPoints[i], mCurPoint)) {
                            mDragType = i;
                            break;
                        }
                    }

                    if (999 == mDragType) {
                        if (mCropRect.contains(mCurPoint.x, mCurPoint.y)) {
                            mDragType = -1;
                        }
                    }

                    mLastPoint.set(mCurPoint.x, mCurPoint.y);
                }
                case MotionEvent.ACTION_MOVE: {
                    onTouchMove();
                    invalidate();
                    mLastPoint.set(mCurPoint.x, mCurPoint.y);
                }
                case MotionEvent.ACTION_UP: {
                    updateCropRectPoints();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

    /**
     * 获取裁剪区域
     *
     * @return
     */
    public RectF getCropRectF() {
        RectF result = new RectF();
        result.left = (mCropRect.left - mCropRectOrigin.left) / mCropRectOrigin.width();
        result.top = (mCropRect.top - mCropRectOrigin.top) / mCropRectOrigin.height();
        result.right = (mCropRect.right - mCropRectOrigin.left) / mCropRectOrigin.width();
        result.bottom = (mCropRect.bottom - mCropRectOrigin.top) / mCropRectOrigin.height();
        return result;
    }

    /**
     * 设置裁剪区域
     *
     * @param rectF
     */
    public void setCropRectF(RectF rectF) {
        mCropRect.left = mCropRectOrigin.left + rectF.left * mCropRectOrigin.width();
        mCropRect.top = mCropRectOrigin.top + rectF.top * mCropRectOrigin.height();
        mCropRect.right = mCropRectOrigin.left + rectF.right * mCropRectOrigin.width();
        mCropRect.bottom = mCropRectOrigin.top + rectF.bottom * mCropRectOrigin.height();
        invalidate();
    }

    /**
     * 初始化
     */
    private void init() {
        mCropRect = new RectF();
        mCropRectOrigin = new RectF();
        mCropPoints = new PointF[8];
        for (int i = 0; i < 8; ++i) {
            mCropPoints[i] = new PointF();
        }

        mCurPoint = new PointF();
        mLastPoint = new PointF();

        mPath = new Path();
        mPaint = new Paint();
        mPaint.setColor(lineColor);
        mPaint.setStyle(Paint.Style.STROKE);

        mValues = new float[9];
    }

    /**
     * 计算两个点之间的距离
     *
     * @param p1
     * @param p2
     * @return
     */
    private float distance(PointF p1, PointF p2) {
        float dx = p1.x - p2.x;
        float dy = p1.y - p2.y;
        return (float) Math.sqrt(dx * dx + dy * dy);
    }

    /**
     * 更新裁剪区域上的8个关键点，用于调整裁剪区域大小
     */
    private void updateCropRectPoints() {
        mCropPoints[0].set(mCropRect.left, mCropRect.bottom);// left,bottom
        mCropPoints[1].set(mCropRect.left, mCropRect.top + mCropRect.height() / 2);// left,middle
        mCropPoints[2].set(mCropRect.left, mCropRect.top);// left,top
        mCropPoints[3].set(mCropRect.left + mCropRect.width() / 2, mCropRect.top);// top,middle
        mCropPoints[4].set(mCropRect.right, mCropRect.top);// right,top
        mCropPoints[5].set(mCropRect.right, mCropRect.top + mCropRect.height() / 2);// right,middle
        mCropPoints[6].set(mCropRect.right, mCropRect.bottom);// right,bottom
        mCropPoints[7].set(mCropRect.left + mCropRect.width() / 2, mCropRect.bottom);// bottom,middle
    }

    /**
     * 1、裁剪区域移动
     * 2、裁剪区域调整大小
     */
    private void onTouchMove() {
        float dx = mCurPoint.x - mLastPoint.x;
        float dy = mCurPoint.y - mLastPoint.y;

        if (0 > mDragType) {// 移动裁剪区域

            // 到达left边界
            if (mCropRect.left + dx < mCropRectOrigin.left) {
                dx = mCropRectOrigin.left - mCropRect.left;
            }

            // 到达top边界
            if (mCropRect.top + dy < mCropRectOrigin.top) {
                dy = mCropRectOrigin.top - mCropRect.top;
            }

            // 到达right边界
            if (mCropRect.right + dx > mCropRectOrigin.right) {
                dx = mCropRectOrigin.right - mCropRect.right;
            }

            // 到达bottom边界
            if (mCropRect.bottom + dy > mCropRectOrigin.bottom) {
                dy = mCropRectOrigin.bottom - mCropRect.bottom;
            }

            mCropRect.offset(dx, dy);

        } else {// 调整裁剪区域大小
            switch (mDragType) {
                case 0: {
                    // 到达left边界
                    if (mCropRect.left + dx < mCropRectOrigin.left) {
                        mCropRect.left = mCropRectOrigin.left;
                    } else {
                        mCropRect.left += dx;

                        // 裁剪区宽度到达最小
                        if (mCropRect.width() < minCropWidth) {
                            mCropRect.left = mCropRect.right - minCropWidth;
                        }
                    }

                    // 到达bottom边界
                    if (mCropRect.bottom + dy > mCropRectOrigin.bottom) {
                        mCropRect.bottom = mCropRectOrigin.bottom;
                    } else {
                        mCropRect.bottom += dy;

                        // 裁剪区高度到达最小
                        if (mCropRect.height() < minCropHeight) {
                            mCropRect.bottom = mCropRect.top + minCropHeight;
                        }
                    }
                    break;
                }
                case 1: {
                    // 到达left边界
                    if (mCropRect.left + dx < mCropRectOrigin.left) {
                        mCropRect.left = mCropRectOrigin.left;
                    } else {
                        mCropRect.left += dx;

                        // 裁剪区宽度到达最小
                        if (mCropRect.width() < minCropWidth) {
                            mCropRect.left = mCropRect.right - minCropWidth;
                        }
                    }
                    break;
                }
                case 2: {
                    // 到达left边界
                    if (mCropRect.left + dx < mCropRectOrigin.left) {
                        mCropRect.left = mCropRectOrigin.left;
                    } else {
                        mCropRect.left += dx;

                        // 裁剪区宽度到达最小
                        if (mCropRect.width() < minCropWidth) {
                            mCropRect.left = mCropRect.right - minCropWidth;
                        }
                    }

                    // 到达top边界
                    if (mCropRect.top + dy < mCropRectOrigin.top) {
                        mCropRect.top = mCropRectOrigin.top;
                    } else {
                        mCropRect.top += dy;

                        // 裁剪区高度到达最小
                        if (mCropRect.height() < minCropHeight) {
                            mCropRect.top = mCropRect.bottom - minCropHeight;
                        }
                    }
                    break;
                }
                case 3: {
                    // 到达top边界
                    if (mCropRect.top + dy < mCropRectOrigin.top) {
                        mCropRect.top = mCropRectOrigin.top;
                    } else {
                        mCropRect.top += dy;

                        // 裁剪区高度到达最小
                        if (mCropRect.height() < minCropHeight) {
                            mCropRect.top = mCropRect.bottom - minCropHeight;
                        }
                    }
                    break;
                }
                case 4: {
                    // 到达right边界
                    if (mCropRect.right + dx > mCropRectOrigin.right) {
                        mCropRect.right = mCropRectOrigin.right;
                    } else {
                        mCropRect.right += dx;

                        // 裁剪区宽度到达最小
                        if (mCropRect.width() < minCropWidth) {
                            mCropRect.right = mCropRect.left + minCropWidth;
                        }
                    }

                    // 到达top边界
                    if (mCropRect.top + dy < mCropRectOrigin.top) {
                        mCropRect.top = mCropRectOrigin.top;
                    } else {
                        mCropRect.top += dy;

                        // 裁剪区高度到达最小
                        if (mCropRect.height() < minCropHeight) {
                            mCropRect.top = mCropRect.bottom - minCropHeight;
                        }
                    }
                    break;
                }
                case 5: {
                    // 到达right边界
                    if (mCropRect.right + dx > mCropRectOrigin.right) {
                        mCropRect.right = mCropRectOrigin.right;
                    } else {
                        mCropRect.right += dx;

                        // 裁剪区宽度到达最小
                        if (mCropRect.width() < minCropWidth) {
                            mCropRect.right = mCropRect.left + minCropWidth;
                        }
                    }
                    break;
                }
                case 6: {
                    // 到达right边界
                    if (mCropRect.right + dx > mCropRectOrigin.right) {
                        mCropRect.right = mCropRectOrigin.right;
                    } else {
                        mCropRect.right += dx;

                        // 裁剪区宽度到达最小
                        if (mCropRect.width() < minCropWidth) {
                            mCropRect.right = mCropRect.left + minCropWidth;
                        }
                    }

                    // 到达bottom边界
                    if (mCropRect.bottom + dy > mCropRectOrigin.bottom) {
                        mCropRect.bottom = mCropRectOrigin.bottom;
                    } else {
                        mCropRect.bottom += dy;

                        // 裁剪区高度到达最小
                        if (mCropRect.height() < minCropHeight) {
                            mCropRect.bottom = mCropRect.top + minCropHeight;
                        }
                    }
                    break;
                }
                case 7: {
                    // 到达bottom边界
                    if (mCropRect.bottom + dy > mCropRectOrigin.bottom) {
                        mCropRect.bottom = mCropRectOrigin.bottom;
                    } else {
                        mCropRect.bottom += dy;

                        // 裁剪区高度到达最小
                        if (mCropRect.height() < minCropHeight) {
                            mCropRect.bottom = mCropRect.top + minCropHeight;
                        }
                    }
                    break;
                }
            }
        }
    }
}
